// Server.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdlib.h>
#include <stdio.h>
#include <Winsock2.h>
#include <process.h>
#include "conio.h"

#pragma comment(lib, "ws2_32.lib")

#define QLEN 5
#define STRLEN 4096
#define SERVPORT 5400
#define WSVERS MAKEWORD(2, 2)
//#define _WINSOCK_DEPRECATED_NO_WARNINGS

#define USER_NOT_EXISTS 400
#define INCORRECT_PASSWORD 401
#define USER_NOT_FOUND 4040
#define NOT_ONLINE 4041
#define REJECT_CHATTING 4042
#define IS_ONLINE 2000
#define START_CHATTING 2001

#define MAXCLNT 64
#define CLIUSERLEN 8
HANDLE hThread[MAXCLNT]; // cli thread pool
int    cln_count = 0;
HANDLE chatRequThread[MAXCLNT]; // thread pool for chatting: request
HANDLE chatDestThread[MAXCLNT]; // thread pool for chatting: destination
int    chat_count = 0;
struct SockInfo {
    SOCKET sock;
    int userId;
} cliSock[MAXCLNT]; // socket pool
struct User {
    int valid;
    char username[CLIUSERLEN + 1];
    char password[CLIUSERLEN + 1];
    int online;
    int lock; // waiting thread lock
    SOCKET sock;
    struct sockaddr_in addr;
} CliUser[MAXCLNT]; // user infomation

unsigned __stdcall send_to_all(void * linksock);

void errExit(const char* errStr, int num = 0) {
    printf(errStr, num);
    return;
}

void testSend(int messageLen);

void initUser();

int findUser(char * message);

int userLogIn(SOCKET linksock, struct sockaddr_in addr);

int getSockUserId(SOCKET linksock);

void writeUserListToBuf(char * buf, int num);

void chat(int userID);

void oneToOneChatStart(int reqId, int destId);

unsigned __stdcall sendToOne(void * userID);

void list(SOCKET linksock);

int main(int argc, char **argv) {
    initUser();
    struct sockaddr_in localSockIn, fromSockIn;
    SOCKET masterSock;
    WSADATA wsadata;
    int addrLen;
    char rBuf[STRLEN], wBuf[STRLEN];
    int messageLen;
    if (WSAStartup(WSVERS, &wsadata) != 0)
        errExit("WSAStartup failed\n");

    // create socket
    masterSock = socket(PF_INET, SOCK_STREAM, 0);
    if (masterSock == INVALID_SOCKET) {
        errExit("failed socket, error number: %d\n", WSAGetLastError());
    }

    // server address
    localSockIn.sin_family = AF_INET;
    localSockIn.sin_port = htons(SERVPORT);
    localSockIn.sin_addr.s_addr = htonl(INADDR_ANY);

    // binding
    if (bind(masterSock, (SOCKADDR*)&localSockIn, sizeof(localSockIn)) == SOCKET_ERROR) {
        errExit("bind() failed: %d\n", WSAGetLastError());
    }

    // listen
    if (listen(masterSock, QLEN) == SOCKET_ERROR) {
        errExit("listen() failed: %d\n", WSAGetLastError());
    }

    // client address length
    addrLen = sizeof(fromSockIn);

    // wait for connection from client...
    while (true) {
        if (cln_count < MAXCLNT) {
            printf("port %d is waiting for connection from %s...\n",
                ntohs(localSockIn.sin_port), inet_ntoa(localSockIn.sin_addr));

            cliSock[cln_count].sock = accept(masterSock, (SOCKADDR*)&fromSockIn, &addrLen);
            if (cliSock[cln_count].sock == INVALID_SOCKET) {
                errExit("eccept() failed: %d\n", WSAGetLastError());
            }
            printf("accept a client whose IP is %s port %d. \n",
                inet_ntoa(fromSockIn.sin_addr), ntohs(fromSockIn.sin_port));

            // struct sockaddr_in clnAddr;
            // int clnAddrLen;
            // memset(&clnAddr, 0, sizeof(clnAddr));
            // int ret = getpeername(lsock, (struct sockaddr*)&cln_addr, &cln_addr_len);

            // the cli log in
            int userId = userLogIn(cliSock[cln_count].sock, fromSockIn);
            if (userId < 0) {
                // log in failed
                continue;
            }
            cliSock[cln_count].userId = userId;
            CliUser[userId].lock = 0; // not locked
            CliUser[userId].sock = cliSock[cln_count].sock;

            // send user list to new cli
            memset(wBuf, 0, sizeof(wBuf));
            writeUserListToBuf(wBuf, cln_count);
            messageLen = send(cliSock[cln_count].sock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);

            // send to all clis, including latest one
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "user %s from IP: %s port number: %d log in. \n",
                CliUser[userId].username, inet_ntoa(fromSockIn.sin_addr), fromSockIn.sin_port);
            for (int i = 0; i <= cln_count; ++i) {
                // fputs("Input message: ", stdout);
                // fgets(wBuf, STRLEN, stdin);
                // scanf_s("%s", wBuf, sizeof(wBuf));
                messageLen = send(cliSock[i].sock, wBuf, strlen(wBuf), 0);
                testSend(messageLen);
            }
            // take the socket into thread pool
            hThread[cln_count] = (HANDLE)_beginthreadex(NULL, 0, &send_to_all, (void *)&cliSock[cln_count], 0, NULL);
            cln_count++;
        }
        else {
            printf("There are too much clis online.\n");
            break;
        }
    }

    WaitForMultipleObjects(cln_count, hThread, TRUE, INFINITE);
    closesocket(masterSock);
    // close thread
    for (int i = 0; i < cln_count; i++) {
        CloseHandle(hThread[i]);
    }
    WSACleanup();

    return 0;
}

unsigned __stdcall send_to_all(void * linksock) {
    int     sock_index;
    struct  sockaddr_in cln_addr;
    int     cln_addr_len;
    char    message[STRLEN];
    char    sendmsg[STRLEN + 100];
    SOCKET  lsock = *((SOCKET*)linksock);
    int recvlen, messageLen;

    // get sock_addr
    int userId = getSockUserId(lsock);
    if (lsock < 0) return 0;
    memset(&cln_addr, 0, sizeof(cln_addr));
    cln_addr = CliUser[userId].addr;
    cln_addr_len = sizeof(cln_addr);

    while (true) {

        while (CliUser[userId].lock) {
            continue;
        }

        memset(&message, 0, sizeof(message));
        recvlen = recv(lsock, message, STRLEN, 0);

        if (recvlen == 0 || recvlen == SOCKET_ERROR) {
            // force downline
            memset(&sendmsg, 0, sizeof(sendmsg));
            sprintf(sendmsg, "user %s from IP: %s port: %d log out. \n",
                CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
            printf("%s", sendmsg);
            CliUser[userId].online = 0;
            for (int i = 0; i < cln_count; i++) {
                if (cliSock[i].sock != lsock) {
                    messageLen = send(cliSock[i].sock, sendmsg, strlen(sendmsg), 0);
                    testSend(messageLen);
                }
                else
                    sock_index = i;
            }
            break;
        }
        else if (recvlen > 0) {
            message[recvlen] = '\0';

            if (strstr(message, "quit") != NULL) {
                // quit
                memset(&sendmsg, 0, sizeof(sendmsg));
                sprintf(sendmsg, "user %s from IP: %s port: %d log out. \n",
                    CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
                printf("%s", sendmsg);
                CliUser[userId].online = 0;
                // inform all users
                for (int i = 0; i < cln_count; i++) {
                    if (cliSock[i].sock != lsock) {
                        messageLen = send(cliSock[i].sock, sendmsg, strlen(sendmsg), 0);
                        testSend(messageLen);
                    }
                    else
                        sock_index = i;
                }

                break;
            }
            else if (!CliUser[userId].lock && strncmp(message, "chat", 5) == 0) {
                // chat request
                chat(userId);
            }
            else if(strncmp(message, "list", 5) == 0) {
                list(lsock);
            }

            /*          else {
            // normal message
            memset(&sendmsg, 0, sizeof(sendmsg));
            sprintf(sendmsg, "user %s from IP: %s port: %d say: %s",
            CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port, message);
            printf("%s", sendmsg);
            // send to all users
            for (int i = 0; i < cln_count; i++) {
            messageLen = send(cliSock[i].sock, sendmsg, strlen(sendmsg), 0);
            testSend(messageLen);
            }
            } */
        }
    }

    // move the sockets behind the log-out one forward
    for (int i = sock_index; i < cln_count - 1; i++) {
        cliSock[i].sock = cliSock[i + 1].sock;
        cliSock[i].userId = cliSock[i + 1].userId;
    }
    cln_count--;

    closesocket(lsock);
    return 0;
}

void testSend(int messageLen) {
    switch (messageLen) {
    case SOCKET_ERROR: errExit("send() failed: %d\n", WSAGetLastError()); break;
    case 0: errExit("send() failed: %d\n", WSAGetLastError()); break;
    default: break;//printf("send() successfully. \n", messageLen);
    }
    return;
}

void initUser() {
    CliUser[0].valid = 1;
    strncpy(CliUser[0].username, "test", 5);
    strncpy(CliUser[0].password, "test", 5);
    CliUser[0].online = 0;
    CliUser[1].valid = 1;
    strncpy(CliUser[1].username, "chenxf", 7);
    strncpy(CliUser[1].password, "chenxf", 7);
    CliUser[1].online = 0;
    CliUser[2].valid = 1;
    strncpy(CliUser[2].username, "remote", 7);
    strncpy(CliUser[2].password, "remote", 7);
    CliUser[2].online = 0;
    CliUser[3].valid = 1;
    strncpy(CliUser[3].username, "cafe", 5);
    strncpy(CliUser[3].password, "cafe", 5);
    CliUser[3].online = 0;
    for (int i = 4; i < MAXCLNT; ++i) {
        CliUser[i].valid = 0;
    }
    return;
}

int findUser(char * message) {
    for (int i = 0; i < MAXCLNT; ++i) {
        if (CliUser[i].valid) {
            if (strstr(message, CliUser[i].username) != NULL)
                return i;
        }
    }

    return MAXCLNT + 1;
}

int userLogIn(SOCKET linksock, struct sockaddr_in addr) {
    SOCKET lsock = linksock;
    char message[STRLEN];
    char sendmsg[STRLEN];
    struct sockaddr_in cln_addr = addr;
    int cln_addr_len = sizeof(cln_addr);
    int messageLen;

    // resv username
    memset(&message, 0, sizeof(message));
    int recvlen = recv(lsock, message, sizeof(message), 0);

    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        memset(&sendmsg, 0, sizeof(sendmsg));
        sprintf(sendmsg, "user from IP: %s port: %d log in failed. \n",
            inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
        printf("%s", sendmsg);
        return -1;
    }
    else if (recvlen > 0) {
        // get username
        // message[recvlen] = '\0';
        int userId = findUser(message); // maybe unsafe
        if (userId < MAXCLNT) {

            // find the user, request for password
            memset(&sendmsg, 0, sizeof(sendmsg));
            sprintf(sendmsg, "user %s from IP: %s port: %d. Please Input password: ",
                CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
            messageLen = send(lsock, sendmsg, strlen(sendmsg), 0);
            testSend(messageLen);

            // get password
            memset(&message, 0, sizeof(message));
            int pwdLen = recv(lsock, message, sizeof(message), 0);

            if (pwdLen == 0 || pwdLen == SOCKET_ERROR) {
                // get password error
                memset(&sendmsg, 0, sizeof(sendmsg));
                sprintf(sendmsg, "user %s from IP: %s port: %d log in failed: get-password error. \n",
                    CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
                printf("%s", sendmsg);
                return -1;
            }
            else if (pwdLen > 0) {
                // got password, confirm it
                // message[pwdLen] = '\0';
                if (strstr(message, CliUser[userId].password) != NULL) {
                    // correct password
                    memset(&sendmsg, 0, sizeof(sendmsg));
                    sprintf(sendmsg, "user %s from IP: %s port: %d log in. \n",
                        CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
                    printf("%s", sendmsg);

                    // log in success, reply the user
                    messageLen = send(lsock, sendmsg, strlen(sendmsg), 0);
                    testSend(messageLen);

                    // change user status
                    CliUser[userId].online = 1;
                    CliUser[userId].addr.sin_family = AF_INET;
                    CliUser[userId].addr.sin_addr = cln_addr.sin_addr;
                    CliUser[userId].addr.sin_port = cln_addr.sin_port;
                    return userId;
                }
                else {
                    // wrong passeord
                    memset(&sendmsg, 0, sizeof(sendmsg));
                    sprintf(sendmsg, "incorrect password: user %s from IP: %s port: %d log in failed. \n",
                        CliUser[userId].username, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
                    printf("%s", sendmsg);
                    // reply INCORRECT_PASSWORD(401)
                    memset(&sendmsg, 0, sizeof(sendmsg));
                    sprintf(sendmsg, "%d", INCORRECT_PASSWORD);
                    messageLen = send(lsock, sendmsg, strlen(sendmsg), 0);
                    testSend(messageLen);
                    return -1;
                }
            }
        }
        else {
            // username not found
            memset(&sendmsg, 0, sizeof(sendmsg));
            sprintf(sendmsg, "user not exists: user %s from IP: %s port: %d log in failed. \n",
                message, inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port);
            printf("%s", sendmsg);
            // reply USER_NOT_EXISTS(400)
            memset(&sendmsg, 0, sizeof(sendmsg));
            sprintf(sendmsg, "%d", USER_NOT_EXISTS);
            messageLen = send(lsock, sendmsg, strlen(sendmsg), 0);
            testSend(messageLen);
            return -1;
        }
    }

    return -1;
}

int getSockUserId(SOCKET linksock) {
    for (int i = 0; i < MAXCLNT; ++i) {
        if (linksock == cliSock[i].sock)
            return cliSock[i].userId;
    }
    return -1;
}

void writeUserListToBuf(char * buf, int num) {
    strcpy(buf, "online user list: \n");
    int i;
    for (i = 0; i < num; ++i) {
        if (CliUser[cliSock[i].userId].online) {
            strncat(buf, CliUser[cliSock[i].userId].username, CLIUSERLEN);
            strcat(buf, "\n");
        }
    }
    char iToStr[4];
    sprintf(iToStr, "%d", i);
    strcat(buf, iToStr);
    strcat(buf, " in total. \n");

    return;
}

void chat(int userID) {
    int userId = userID;
    SOCKET lsock = CliUser[userId].sock;
    char wBuf[STRLEN];
    char rBuf[STRLEN];
    int messageLen;
    int recvlen;

    // block recv thread in cli
    memset(wBuf, 0, sizeof(wBuf));
    sprintf(wBuf, "If you do want to chat, ");
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    // ask which chatting: one-to-one or one-to-more
    memset(wBuf, 0, sizeof(wBuf));
    sprintf(wBuf, "Send 'one' to start one-to-one chatting or 'group' to start more-to-more chatting : ");
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    memset(rBuf, 0, sizeof(rBuf));
    recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        printf("start chatting failed: choice not received. \n");
        return;
    }
    else if (recvlen > 0) {
        rBuf[recvlen] = '\0';
        if (strstr(rBuf, "one") != NULL) {
            // one-to-one chatting

            // request for who it'll chatting with
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "send the user's name you'll chat with: ");
            messageLen = send(lsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);

            // got destination username
            memset(rBuf, 0, sizeof(rBuf));
            recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
            rBuf[recvlen - 1] = '\0';
            if (recvlen == 0 || recvlen == SOCKET_ERROR) {
                printf("start chatting failed: dest user not received. \n");
                return;
            }
            else if (recvlen > 0) {
                // find the user
                rBuf[recvlen] = '\0';
                int destUserId = findUser(rBuf);

                if (destUserId > MAXCLNT) {
                    // user not found
                    memset(wBuf, 0, sizeof(wBuf));
                    sprintf(wBuf, "%d", USER_NOT_FOUND);
                    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
                    testSend(messageLen);
                    printf("user %s is not found. \n", rBuf);
                    return;
                }

                if (CliUser[destUserId].online) {
                    // destination user is online
                    memset(wBuf, 0, sizeof(wBuf));
                    sprintf(wBuf, "%d", IS_ONLINE);
                    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
                    testSend(messageLen);
                    printf("user %s is online. \n", rBuf);

                    // begin chatting forward
                    CliUser[destUserId].lock = 1; // locked
                    oneToOneChatStart(userId, destUserId);
                    CliUser[destUserId].lock = 0; // unlocked
                    return;
                }
                else {
                    // destination user isn't online
                    memset(wBuf, 0, sizeof(wBuf));
                    sprintf(wBuf, "%d", NOT_ONLINE);
                    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
                    testSend(messageLen);
                    printf("user %s isn't online. \n", rBuf);
                    return;
                }
            }
        }
        else if (strstr(rBuf, "group") != NULL) {
            // more-to-more chatting
            // 
        }
    }

    return;
}

void oneToOneChatStart(int reqId, int destId) {
    char rBuf[STRLEN];
    char wBuf[STRLEN];
    SOCKET reqSock = CliUser[reqId].sock;
    SOCKET destSock = CliUser[destId].sock;
    int messageLen;
    int recvlen;

    memset(wBuf, 0, sizeof(wBuf));
    sprintf(wBuf, "user %s want to chat with you. Send 'chat confirm' to start it or 'chat reject' instead. \n",
        CliUser[reqId].username);
    messageLen = send(destSock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    memset(rBuf, 0, sizeof(rBuf));
    recvlen = recv(destSock, rBuf, sizeof(rBuf), 0);
    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        printf("error in starting chatting. \n");
        return;
    }
    else if (recvlen > 0) {
        if (strstr(rBuf, "chat confirm") != NULL) {
            // tell req user that chat starting
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "%d", START_CHATTING);
            messageLen = send(reqSock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            printf("%s begin chat with %s...\n", CliUser[reqId].username,
                CliUser[destId].username);

            // start two thread to forward message
            int chatId = chat_count;
            int id1[2], id2[2];
            id1[0] = reqId; id1[1] = destId;
            id2[0] = destId; id2[1] = reqId;
            chatRequThread[chat_count] = (HANDLE)_beginthreadex(NULL, 0, &sendToOne, (void *)&id1, 0, NULL);
            chatDestThread[chat_count] = (HANDLE)_beginthreadex(NULL, 0, &sendToOne, (void *)&id2, 0, NULL);
            chat_count++;

            WaitForSingleObject(chatRequThread[chatId], INFINITE);
            WaitForSingleObject(chatDestThread[chatId], INFINITE);

            // end chatting
            // move the thread behind the log-out one forward
            CloseHandle(chatRequThread[chatId]);
            CloseHandle(chatDestThread[chatId]);
            for (int i = chatId; i < chat_count - 1; i++) {
                chatRequThread[i] = chatRequThread[i + 1];
                chatDestThread[i] = chatDestThread[i + 1];
            }
            chat_count--;
            return;
        }
        else {
            // tell req user that chat reject
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "%d", REJECT_CHATTING);
            messageLen = send(reqSock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            return;
        }
    }

    return;
}

unsigned __stdcall sendToOne(void * userID) {
    int     userIdRecv = ((int*)userID)[0];
    int     userIdSend = ((int*)userID)[1];
    char recvUserName[CLIUSERLEN + 1];
    char sendUserName[CLIUSERLEN + 1];
    strncpy(recvUserName, CliUser[userIdRecv].username, strlen(CliUser[userIdRecv].username) + 1);
    strncpy(sendUserName, CliUser[userIdSend].username, strlen(CliUser[userIdSend].username) + 1);
    SOCKET  recvSock = CliUser[userIdRecv].sock;
    SOCKET  sendSock = CliUser[userIdSend].sock;
    char    message[STRLEN];
    char    sendmsg[STRLEN];
    int recvlen, messageLen;

    while (true) {
        memset(&message, 0, sizeof(message));
        recvlen = recv(recvSock, message, sizeof(message), 0);

        if (recvlen == 0 || recvlen == SOCKET_ERROR) {
            memset(&sendmsg, 0, sizeof(sendmsg));
            sprintf(sendmsg, "user %s leave chatting %s & %s. \n",
                recvUserName, recvUserName, sendUserName);
            printf("%s", sendmsg);
            messageLen = send(sendSock, sendmsg, strlen(sendmsg), 0);
            testSend(messageLen);
            // chatting end
            break;
        }
        else if (recvlen > 0) {
            message[recvlen] = '\0';

            if (strstr(message, "quit") != NULL) {
                // quit
                memset(&sendmsg, 0, sizeof(sendmsg));
                sprintf(sendmsg, "user %s leave chatting %s : %s. \n",
                    recvUserName, recvUserName, sendUserName);
                printf("%s", sendmsg);
                messageLen = send(sendSock, sendmsg, strlen(sendmsg), 0);
                testSend(messageLen);
                return 1;
            }
            else {
                // normal message
                memset(&sendmsg, 0, sizeof(sendmsg));
                sprintf(sendmsg, "%s: %s", recvUserName, message);
                // printf("%s", sendmsg);
                messageLen = send(sendSock, sendmsg, strlen(sendmsg), 0);
                testSend(messageLen);
            }
        }
    }

    return 1;
}

void list(SOCKET linksock) {
    SOCKET lsock = linksock;
    char wBuf[STRLEN];
    int messageLen;

    // send all online user
    memset(wBuf, 0, sizeof(wBuf));
    writeUserListToBuf(wBuf, cln_count);
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    return ;
}
